/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bookmarks Sync.
 *
 * The Initial Developer of the Original Code is Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Harini Achala Sirisena <hariniachala@gmx.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

const EXPORTED_SYMBOLS = ['MailNewsEngine'];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/stores.js");
Cu.import("resource://services-sync/trackers.js");
Cu.import("resource://mailnews-sync/type_records/mailnews.js");
Cu.import("resource://services-sync/engines/clients.js");
Cu.import("resource://services-sync/constants.js");

var tagService = Cc["@mozilla.org/messenger/tagservice;1"].getService(Ci.nsIMsgTagService);
var recordsToCache = [];

function getRootFolderList()
{
  var acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
                   .getService(Ci.nsIMsgAccountManager);
  var rootFolderList = new Array();
  for (var i = 0; i < acctMgr.accounts.Count(); i++) {
    var account = acctMgr.accounts.QueryElementAt(i, Ci.nsIMsgAccount);
    var rootFolder = account.incomingServer.rootFolder;
    if (rootFolder.server.type =="rss" || rootFolder.server.type == "nntp") {
      rootFolderList.push(rootFolder);
    }
  }
  
  return rootFolderList;
}

function getRootFeedFolderList()
{
  var acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
                   .getService(Ci.nsIMsgAccountManager);
  var rootFolderList = new Array();
  for (var i = 0; i < acctMgr.accounts.Count(); i++) {
    var account = acctMgr.accounts.QueryElementAt(i, Ci.nsIMsgAccount);
    var rootFolder = account.incomingServer.rootFolder;
    if (rootFolder.server.type == "rss") {
      rootFolderList.push(rootFolder);
    }
  }
  
  return rootFolderList;
}

function getRootNewsgroupFolderList()
{
  var acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
                   .getService(Ci.nsIMsgAccountManager);
  var rootFolderList = new Array();
  for (var i = 0; i < acctMgr.accounts.Count(); i++) {
    var account = acctMgr.accounts.QueryElementAt(i, Ci.nsIMsgAccount);
    var rootFolder = account.incomingServer.rootFolder;
    if (rootFolder.server.type == "nntp") {
      rootFolderList.push(rootFolder);
    }
  }
  
  return rootFolderList;
}
   
function getSubFolderList(rootFolderList)
{
  var subFolderList = new Array();
  for (var i = 0; i < rootFolderList.length; i++) {
    var subFolders = rootFolderList[i].subFolders;
    while(subFolders.hasMoreElements()) {
      var folder = subFolders.getNext().QueryInterface(Ci.nsIMsgFolder);
      if (folder.server.type == "nntp" || folder.server.type == "rss")
        subFolderList.push(folder);
    }	
  }
  
  return subFolderList;
}

function getNewsgroupFolder(newsgroup)
{
  var newsgroupFolder = null;
  var rootFolderList = getRootNewsgroupFolderList();
  for (var i = 0; i < rootFolderList.length; i++) {
    var subFolders = rootFolderList[i].subFolders;
    while(subFolders.hasMoreElements()) {
      var folder = subFolders.getNext().QueryInterface(Ci.nsIMsgFolder);
      if (folder.server.type =="nntp" && folder.name == newsgroup) {
        newsgroupFolder = folder;
        break;
      }
    }	
  }
  
  return newsgroupFolder;
}
   
function getMsgDbList(subFolderList)
{
  var msgDbList = new Array();
  for (var i = 0; i < subFolderList.length; i++) {
    msgDbList.push(subFolderList[i].msgDatabase);
  }
  
  return msgDbList;
}

function checkFeedSubscribed(feedUrl, msgDbList)
{
  var feed = "";
  var subscribed = false;
  for(var i = 0; i < msgDbList.length; i++) {
    feed = msgDbList[i].QueryInterface(Ci.nsIMsgDatabase)
                       .dBFolderInfo.getCharProperty("feedUrl");
    if(feedUrl == feed) {
      subscribed = true;
    }
  }
  
  return subscribed;
}
  
function checkNewsGroupSubscribed(newsgroupName, folderList)
{
  var newsgroup = "";
  var subscribed = false;
  for(var i = 0; i < folderList.length; i++) {
    newsgroup = folderList[i].QueryInterface(Ci.nsIMsgFolder).name;
    if(newsgroupName.indexOf(newsgroup) != -1) {
      subscribed = true;
    }
  }
  
  return subscribed;
}

function updateHeader(record, msgDbList, cache) {
  var msgHdr = null;
  var tags = null;
  for(var i = 0; i < msgDbList.length; i++) {
    msgHdr = msgDbList[i].getMsgHdrForMessageID(record.messageId);
    
    var readStatus = false;
    var flagged = false;
    
    if(record.readStatus == "true" || record.readStatus == true) {
    	readStatus = true;
    }
    if(record.flagged == "true" || record.flagged == true) {
    	flagged = true;
    }
        
    if(msgHdr!=null) {
      msgHdr.setStringProperty("weave", "synced");
      msgHdr.markRead(readStatus);
      msgHdr.markFlagged(flagged);
      if(!cache) {
      	tags = JSON.parse(record.tags);
      } else {
      	  tags = record.tags;
      }
          
      if(tags.length > 0) {  
        var keywordsToSet = "";
        for(var j = 0; j < tags.length; j++) {
          tagService.addTagForKey(tags[j]["tagId"], tags[j]["tagName"], tags[j]["tagColor"], ""); //ordinal not synced
          keywordsToSet = keywordsToSet + tags[j]["tagId"] + " ";
        }
              
        keywordsToSet = keywordsToSet.trim();
        msgHdr.setStringProperty("keywords", keywordsToSet);
      }
    }
  }
}
  
function saveToFile(filePath, val)
{
  filePath = "weave/" + filePath + ".json";      
  let pathParts = filePath.split("/");
  
  let file = Svc.Directory.get("ProfD", Ci.nsIFile);
  file.QueryInterface(Ci.nsILocalFile);
  for (let i = 0; i < pathParts.length; i++) {
    file.append(pathParts[i]);
  }
  
  let cachedRecords = null;
  if (!file.exists()) {
    file.create(file.NORMAL_FILE_TYPE, PERMS_FILE);
    cachedRecords = val;
  } else {
      let [is] = Utils.open(file, "<");
      let jsonStream = Utils.readStream(is);
      is.close();
      cachedRecords = JSON.parse(jsonStream);
      cachedRecords = cachedRecords.concat(val);
  }
        
  let out = JSON.stringify(cachedRecords);
  let [fos] = Utils.open(file, ">");
  fos.writeString(out);
  fos.close();
}

function MailNewsEngine()
{
  SyncEngine.call(this, "MailNews");
  this._log.debug("Mailnews Engine Started");
  var prefs = Cc["@mozilla.org/preferences-service;1"]
                .getService(Ci.nsIPrefService);
  var branch = prefs.getBranch("mailnews.sync.caching.");
  var applyCache =branch.getBoolPref("applyonstartup");
  if (applyCache)
    this.applyCachedRecords();
}
MailNewsEngine.prototype = {
  __proto__: SyncEngine.prototype,
  version: 2,
  _storeObj: MailNewsStore,
  _trackerObj: MailNewsTracker,
  _recordObj: MailNewsSetRecord,

  _syncStartup: function _syncStart() {
    var prefs = Cc["@mozilla.org/preferences-service;1"]
                      .getService(Ci.nsIPrefService);
    var branch = prefs.getBranch("mailnews.sync.accounts.");
    var accountsToSync = branch.getCharPref("fullupload");
    if(accountsToSync != "") {
      var accounts = accountsToSync.split(",");
      for( var i = 0; i < (accounts.length - 1); i++ ) {
      	if(accounts[i] != null)
      	  this.syncNewsgroup(accounts[i]);
      }
      branch.setCharPref("fullupload", "");
    }
    SyncEngine.prototype._syncStartup.call(this);
  },
  
  _syncFinish: function _syncFinish() {
    SyncEngine.prototype._syncFinish.call(this);
    if ( recordsToCache.length > 0 ) {
      try {
      saveToFile("mailnews/mailnewsReSync", recordsToCache);
      } catch (e) {
      	  this._log.debug("Error caching records: " + e.toString());
      }
      recordsToCache = [];
    }
    
  },
  
  syncNewsgroup: function syncNewsgroup(newsgroup)
  {
    this._log.debug("Up sync of newsgroup: " + newsgroup);
    try {
    var guid = null;
    var msgHdr =null;
    var folder = getNewsgroupFolder(newsgroup);
    if(folder == null) {
      this._log.debug("Newsgroup " + newsgroup + " not found.");
      return;
    }
    var msgDb = folder.msgDatabase;
    
    var enumerator = msgDb.EnumerateMessages();
    while (enumerator.hasMoreElements()) {
      msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
      if ( msgHdr.isRead || msgHdr.isFlagged || (msgHdr.getStringProperty("keywords") != "") ) {
 	    guid = Utils.makeGUID() + "@" + msgHdr.messageKey + "@" + msgHdr.dateInSeconds;
  	    this._tracker.addChangedID(guid);
      }
    }
    }
    catch(e) {
    	this._log.debug("Initial upsync of " + newsgroup + " failed: " + e.toString());
    }
  },
  
  applyCachedRecords: function MailNewsEngine_applyCachedRecords()
  {
    var cachedRecords = null;
    try {
      Utils.jsonLoad("mailnews/mailnewsReSync", this, function(json) {
        cachedRecords =  json;
      });
      
      if(!(cachedRecords == null)) {
        for(var h = 0; h < cachedRecords.length; h++) {
          var record =  cachedRecords[h];        
          var subscribed = false; //set to false by default 
          var rootFolderList = getRootFolderList();
          var subFolderList = getSubFolderList(rootFolderList);
          var msgDbList = getMsgDbList(subFolderList);
   
          if(record.serverType == "nntp") {
            subscribed = checkNewsGroupSubscribed(record.accountInfo, subFolderList);
          }
          if(record.serverType == "rss") {
            subscribed = checkFeedSubscribed(record.accountInfo, msgDbList);
          }
      
          if(subscribed) {
            updateHeader(record, msgDbList, true);
            this._log.debug("Applied cached record ID: "+ record.messageId 
                           +" , FLAGGED: " + record.flagged 
                           + " , READ: "+record.readStatus
                           +" , TAG: "+JSON.stringify(record.tags));
            delete cachedRecords[h];
          } else {
          	  this._log.debug("Cannot apply record from cache. Not subscribed to: "+ record.accountInfo);
          }
        }
        
        cachedRecordsCopy = [];
        for(var j = 0; j < cachedRecords.length; j++) {
        	if(cachedRecords[j] != null) {
        		cachedRecordsCopy.push(cachedRecords[j]);
        	}
        }
        Utils.jsonSave("mailnews/mailnewsReSync", this, cachedRecordsCopy);//update cache
      }
    }  
    catch (e) {
      this._log.debug("Error applying cached mailnews record: "+e.toString());
    }
  },
    
};

function MailNewsStore(name) {
  Store.call(this, name);
}
MailNewsStore.prototype = {
  __proto__: Store.prototype,
  downCount : 1,

  getAllIDs: function MailNewsStore_getAllIDs()
  {
    this._log.debug("Constructing initial mailnews headers of feeds for up sync");
    var items = {};
    var guid = null;
    var msgHdr =null;
    var rootFolderList = getRootFeedFolderList();
    var subFolderList = getSubFolderList(rootFolderList);
    var msgDbList = getMsgDbList(subFolderList);
    
    for (var i = 0; i < msgDbList.length; i++) {
      var enumerator = msgDbList[i].EnumerateMessages();
      while (enumerator.hasMoreElements()) {
        msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
        if ( msgHdr.isRead || msgHdr.isFlagged || (msgHdr.getStringProperty("keywords") != "") ) {
 	      guid = Utils.makeGUID() + "@" + msgHdr.messageKey + "@" + msgHdr.dateInSeconds;
  	      items[guid] = this.createRecord(guid);
        }
      }
    }
  	
    return items;
  },

  createRecord: function createRecord(guid)
  {
    // Creates a record to be synced-up to server
    var tempArray = guid.split("@");
    var key = tempArray[1];
    var dateInSeconds = tempArray[2];
    record = new MailNewsSetRecord();
    var isRead = false;
    var isFlagged = false;
    var messageId = "";
    var tagN = "";
    var tagC = "";
    var tagId = "";
    var url = "";
    var serverType = "";
    var dbHdr = null;
    
    try{
      var rootFolderList = getRootFolderList();
      var subFolderList = getSubFolderList(rootFolderList);
      var msgDbList = getMsgDbList(subFolderList);
      for(var i = 0; i < msgDbList.length; i++){
        var exists = msgDbList[i].QueryInterface(Ci.nsIMsgDatabase).ContainsKey(key);
        if(exists) {
 	 	  dbHdr = msgDbList[i].QueryInterface(Ci.nsIMsgDatabase).GetMsgHdrForKey(key);
 	 	  if(dbHdr.dateInSeconds == dateInSeconds) { //correct match
 	 	    url = msgDbList[i].QueryInterface(Ci.nsIMsgDatabase)
 	 	                      .dBFolderInfo.getCharProperty("feedUrl");//TODO: rewrite this code
            break;
          }
        }
      }
      
      if(dbHdr != null) {
        isRead = dbHdr.isRead;
    	isFlagged = dbHdr.isFlagged;
    	messageId = dbHdr.messageId;
    	tagId = dbHdr.getStringProperty("keywords");
    	var tagJson = [];
    	if (tagId != "") {
    	  var tagIdArray = tagId.split(' ');
    	  for(var i  =0; i <tagIdArray.length ; i++) {
    	    tagN = tagService.getTagForKey(tagIdArray[i]);
    	    tagC = tagService.getColorForKey(tagIdArray[i]);
    	    tagJson.push({tagId: tagIdArray[i], tagName: tagN, tagColor: tagC});
    	  }
    	}
    	
    	serverType = dbHdr.folder.server.type;
    	if(serverType == "nntp") {
    	  url = dbHdr.folder.name;
    	}
    	//this._log.debug( "ID: "+messageId + " , FLAGGED: " + isFlagged + " , READ: "+isRead+" , TAG: " +JSON.stringify(tagJson));
      }
      
      record.id = Utils.makeGUID();
      record.messageId = messageId;
      record.flagged = isFlagged;
      record.readStatus = isRead;
      record.tags = JSON.stringify(tagJson);
      record.accountInfo = url;
      record.serverType = serverType;
      
      return record;
    }
    catch(e) {
      this._log.debug("Error in creating mailnews record: "+e.toString());
    }
  },
  
  itemExists: function MailNewsStore_itemExists(id) {
    // Returns true by default because no new mailnews items are created. 
    // Only headers of existing items are synced.
    return true;
  },  

  create: function MailNewsStore_create(record) {
    // Not implemented because no new mailnews items are created.
    // Only headers of existing items are updated.
  },
  
  update: function MailNewsStore_update(record) {
    // Sync down updated mailnews headers from other clients.
    this._log.debug("(" + this.downCount + ")Down sync of mailnews header, ID: "+record.messageId 
                       + " , URL: "+record.accountInfo+" , FLAGGED: "+record.flagged
                       +" , READ: "+record.readStatus+" , TAG: "+record.tags);
    this.downCount++;
    try{
      var subscribed = false; //set to false by default
      var syncOn = true;
    
      var rootFolderList = getRootFolderList();
      var subFolderList = getSubFolderList(rootFolderList);
      var msgDbList = getMsgDbList(subFolderList);
     
      if(record.serverType == "nntp") {
        subscribed = checkNewsGroupSubscribed(record.accountInfo, subFolderList);
        
	    var prefs = Cc["@mozilla.org/preferences-service;1"]
                      .getService(Ci.nsIPrefService);
        var branch = prefs.getBranch("mailnews.sync.account.nntp.");
        var children = branch.getChildList("", {});
        var sync = false;
        for (var i = 0; i < children.length; i++ ) {
          if( record.accountInfo == children[i] ) {
            sync = branch.getBoolPref(record.accountInfo);
          }
        }
	    if (!sync) {
	      syncOn = false;
	      this._log.debug("Sync for: " + record.accountInfo + " is " + sync);
	    }
	  }
      
      if(record.serverType == "rss") {
        subscribed = checkFeedSubscribed(record.accountInfo, msgDbList);
      }
        
      if(subscribed && syncOn) {
        updateHeader(record, msgDbList, false);      
      } else { //cache for syncing later when subscribed to
          
          var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefService).getBranch("mailnews.sync.");
          var cachingEnabled = prefs.getBoolPref("caching.enabled");

          if(cachingEnabled) {
            this._log.debug("Caching mailnews header record for: " + record.accountInfo );
            var recordString = '{"messageId" : "'+record.messageId
                                  +'", "accountInfo" : "'+record.accountInfo
                                  +'", "serverType" : "'+record.serverType
                                  +'", "flagged" : "'+record.flagged
                                  +'", "readStatus" : "'+record.readStatus
                                  +'", "tags" : '+record.tags+' }';
            var jsonRecord = JSON.parse(recordString);
            recordsToCache.push(jsonRecord);
            //saveToFile("mailnews/mailnewsReSync", recordString);
          } else {
          	  this._log.debug("Caching is disabled" );
          }
      }
    }
    catch(e) {
      this._log.debug("Error in down-sync of mailnews: "+e.toString());
    }
  },
  
  remove: function MailNewsStore_remove(record) {
    //To be implemented later when MailNews engine syncs mailnews item deletions.
  },

  wipe: function MailNewsStore_wipe() {
    // Not implemented because wiping of all mailnews headers should not be permitted.
  },
        
};

function MailNewsTracker(name) {
  Tracker.call(this, name);
  var nsIFolderListener = Ci.nsIFolderListener;
  var notifyFlags = nsIFolderListener.propertyFlagChanged;
  var mailSession = Cc["@mozilla.org/messenger/services/session;1"]
                       .getService(Ci.nsIMsgMailSession);
  mailSession.AddFolderListener(this, notifyFlags);
}

MailNewsTracker.prototype = {
  __proto__: Tracker.prototype,
   
  OnItemPropertyFlagChanged: function (item, property, oldValue, newValue)
  {
    if(property == "Status"|| property == "Keywords" || property == "Flagged") {
      
      //check if this header was just down-synced by weave, if so return.
	  if(item.getStringProperty("weave") == "synced") {
	    item.setStringProperty("weave", ""); //reset
	    return;
	  }
	  //check if this is a newsgroup msg that should be synced, if not return.
	  if(item.folder.server.type == "nntp") {
	    var prefs = Cc["@mozilla.org/preferences-service;1"]
                      .getService(Ci.nsIPrefService);
        var branch = prefs.getBranch("mailnews.sync.account.nntp.");
        var children = branch.getChildList("", {});
        var sync = false;
        for (var i = 0; i < children.length; i++ ) {
          if( item.folder.name == children[i] ) {
            sync = branch.getBoolPref(item.folder.name);
          }
        }
        this._log.debug("Sync for: " + item.folder.name + " is " + sync);
	    if (!sync)
	      return;
	    
	    //else check if this is from a newsgroup that is just about to be upsynced in total, if so return.
	    var branch = prefs.getBranch("mailnews.sync.accounts.");
        var accountsToSync = branch.getCharPref("fullupload");
        if(accountsToSync != "") {
          var accounts = accountsToSync.split(",");
          for( var i = 0; i < (accounts.length - 1); i++ ) {
      	    if(accounts[i] == item.folder.name )
      	      return;
          }
        }
	  }
	  
	  var guid = Utils.makeGUID() + "@" + item.messageKey +"@"+item.dateInSeconds;
	  this.addChangedID(guid);
      // Update the score as you see fit:
      this._score += 100;
      this._log.debug("Changed item, ID: "+item.messageId 
                       + " , FLAGGED: "+item.isFlagged
                       +" , READ: "+item.isRead
                       +" , TAG: "+item.getStringProperty("keywords"));
	}
  }
};
